package vip.liux.gateway.filters.auditlog;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Objects;

import static vip.liux.gateway.consts.GlobalConstant.writeValueAsString;

@Component
public class AuditRequestLogGlobalFilter implements GlobalFilter, Ordered {

    private static final Logger logger = LoggerFactory.getLogger(AuditRequestLogGlobalFilter.class);

    private AuditRequestLogRepository auditRequestLogRepository = new InMemoryAuditRequestLogRepository();

    @Autowired(required = false)
    public void setAuditRequestLogRepository(AuditRequestLogRepository auditRequestLogRepository) {
        this.auditRequestLogRepository = auditRequestLogRepository;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) {
        AuditRequestLog log = new AuditRequestLog();

        // 请求时间
        log.setExecutionTime(LocalDateTime.now());

        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();

        // 访问实例名（可根据路由id或host header等自定义）
        String serviceName = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR) != null
                ? Objects.toString(exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR))
                : "";
        log.setServiceName(serviceName);

        // 请求信息
        log.setUrl(request.getURI().toString());
        log.setMethod(request.getMethod().name());
        log.setSchema(request.getURI().getScheme());

        // 浏览器信息
        log.setBrowserInfo(headers.getFirst("User-Agent"));

        // 客户端IP
        String ip = headers.getFirst("X-Forwarded-For");
        if (ip == null && request.getRemoteAddress() != null) {
            ip = request.getRemoteAddress().getAddress().getHostAddress();
        }
        log.setClientIpAddress(ip);

        // 记录响应体、响应时间、耗时
        long startTime = System.currentTimeMillis();

        String contentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);

        Mono<Void> bodyReadMono;
        if (contentType != null && contentType.contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
            // 处理 form 表单数据
            bodyReadMono = exchange.getFormData().doOnNext(formData -> log.setParameters(writeValueAsString(formData))).then();
        } else if (contentType != null && contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
            // 处理 JSON 请求体
            Object cachedBody = exchange.getAttribute(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR);
            if (cachedBody != null) {
                log.setRequestBody(cachedBody.toString());
            }
            MultiValueMap<String, String> queryParams = request.getQueryParams();
            log.setParameters(writeValueAsString(queryParams));
            bodyReadMono = Mono.empty();
        } else {
            // 其他类型，仅记录 query 参数
            MultiValueMap<String, String> queryParams = request.getQueryParams();
            log.setParameters(writeValueAsString(queryParams));
            bodyReadMono = Mono.empty();
        }

        // 包装响应以捕获响应内容
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferCollectorResponseDecorator decoratedResponse =
                new DataBufferCollectorResponseDecorator(originalResponse);

        ServerWebExchange mutatedExchange = exchange.mutate().response(decoratedResponse).build();

        return bodyReadMono.then(
                chain.filter(mutatedExchange).then(
                        Mono.defer(() -> {
                            log.setResponseTime(LocalDateTime.now());
                            log.setExecutionDuration(System.currentTimeMillis() - startTime);

                            // 响应体
                            log.setResponseData(decoratedResponse.getFullBody());

                            // 实际项目建议用 logger，这里演示直接打印
                            logRequestWithDetails(log);
                            return Mono.empty();
                        })
                )
        );
    }

    /**
     * 打印日志
     *
     * @param log 网关日志对象
     */
    private void logRequestWithDetails(AuditRequestLog log) {
        logger.info("AUDIT_LOG: {}", writeValueAsString(log));
        auditRequestLogRepository.add(log);
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE; // 靠后执行，确保拿到完整信息
    }

    /**
     * 用于收集响应内容的装饰器
     */
    static class DataBufferCollectorResponseDecorator extends ServerHttpResponseDecorator {

        private final ByteArrayOutputStream bodyStream = new ByteArrayOutputStream();

        public DataBufferCollectorResponseDecorator(ServerHttpResponse delegate) {
            super(delegate);
        }

        @Override
        public Mono<Void> writeWith(org.reactivestreams.Publisher<? extends org.springframework.core.io.buffer.DataBuffer> body) {
            if (body instanceof Flux<? extends DataBuffer> flux) {
                return super.writeWith(
                        flux.doOnNext(dataBuffer -> {
                            byte[] content = new byte[dataBuffer.readableByteCount()];
                            dataBuffer.read(content);
                            bodyStream.write(content, 0, content.length);
                        })
                );
            }
            return super.writeWith(body);
        }

        public String getFullBody() {
            return bodyStream.size() > 0
                    ? bodyStream.toString(StandardCharsets.UTF_8)
                    : null;
        }
    }
}